﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using MR_HKSUtility;

namespace MR_HKSCalc
{
    /**
     * Multimedia Retrieval
     * Heat Kernel Signature project
     * Frank Borgonjen, Joeri van der Lei & Kevin van der Lei
     * 28-10-2011
     * 
     * The HKSCalculator class is used to calculate heat kernel signature values using pre-computed values
     * for the eigenvalus and eigenvectors.
     */
    public class HKSCalculator
    {
        #region Functions

        /**
         * Calculates the heat kernel signature values for the given eigenvalues and eigenvectors and stores 
         * the result in the given directory.
         * @param eigenvectorFile the full path name of the file containing the eigenvectors to be used.
         * @param eigenvectorFile the full path name of the file containing the eigenvalues to be used.
         * @param hksDirectory the directory in which the HKS values are to be stored.
         */
        public static void CalculateHKT(String eigenvectorFile, String eigenvalueFile, String hksDirectory)
        {
            List<List<double>> eigenvectors = new List<List<double>>();
            List<double> eigenvalues = new List<double>();

            TextReader reader = new StreamReader(eigenvectorFile);

            string line = reader.ReadLine();
            double value;
            for(int i = 0; line != null; i++)
            {
                string[] split = line.Split(",".ToCharArray());
                if (eigenvectors.Count == 0)
                    for (int j = 0 ; j < split.Length; j++)
                        eigenvectors.Add(new List<double>());
                for(int j = 0; j < split.Length; j++)
                {
                    value = Double.Parse(split[j].Replace(".", ","), System.Globalization.NumberStyles.Float);
                    eigenvectors[j].Add(value);
                }
                line = reader.ReadLine();
            }

            reader.Close();

            reader = new StreamReader(eigenvalueFile);

            line = reader.ReadLine();
            for (int i = 0; line != null; i++)
            {
                string[] split = line.Split(",".ToCharArray());
                value = Double.Parse(split[i].Replace(".", ","), System.Globalization.NumberStyles.Float);
                eigenvalues.Add(value);
                line = reader.ReadLine();
            }

            reader.Close();

            double[,] eigenvectormatrix = new double[eigenvectors.Count, eigenvectors[0].Count];
            for (int i = 0; i < eigenvectormatrix.GetLength(0); i++)
                for (int j = 0; j < eigenvectormatrix.GetLength(1); j++)
                    eigenvectormatrix[i, j] = eigenvectors[i][j];
            
            //Calculate values for t
            double tmin, tmax, stepsize;
            tmin = 4 * Math.Log(10) / eigenvalues[1];
            tmax = 4 * Math.Log(10) / eigenvalues[eigenvalues.Count - 1];
            stepsize = (Math.Log(tmax) - Math.Log(tmin)) / 100;

            double[] hksvals = new double[eigenvalues.Count];
            for (int x = 0; x < eigenvectormatrix.GetLength(0); x++)
            {
                double hks = 0;
                for (int i = 0; i < 100; i++)
                {
                    double todiv = 0;
                    for (int j = 0; j < eigenvalues.Count; j++)
                        todiv += Math.Exp(-(Math.Log(tmin) + i* stepsize) * eigenvalues[j]);
                 
                    hks += HKT(x, Math.Log(tmin) + i * stepsize, eigenvectormatrix, eigenvalues) / todiv;
                }
                hksvals[x] = hks;
            }

            Storage s = new Storage(hksDirectory);
            s.saveHKSValues(Path.GetFileNameWithoutExtension(eigenvectorFile), hksvals);            
        }

        /**
         * Returns a heat kernel value for the given parameters.
         * @param x the index of the eigenvalue and eigenvector to be used.
         * @param t the 'time' value used to calculate the amount of heat transferred.
         * @param eigenvectors a double array containing a certain amount of eigenvectors for each eigenvector its values.
         * @param eigenvalues a List containing the eigenvalues associated with the given eigenvectors.
         */
        public static double HKT(int x, double t, double[,] eigenvectors, List<double> eigenvalues)
        {
            double result = 0;
            for (int i = 0; i < eigenvectors.GetLength(0); i++)
            {
                result += (Math.Exp(-eigenvalues[i] * t) * eigenvectors[i,x] * eigenvectors[i,x]);
            }
            return result;
        }

        /**
         *  Calculates the Euclidean distance between two vectors.
         *  @param vector1 the first vector.
         *  @param vector2 the second vector.
         */
        private static double euclidianDistance(double[] vector1, double[] vector2)
        {
            double distance = 0;
            for (int i = 0; i < vector1.GetLength(0); i++)
            {
                distance += Math.Pow(vector1[i] - vector2[i], 2);
            }
            return Math.Sqrt(distance);
        }

        /**
         * The main method takes the given path name and computes the complete distance matrix for the files
         * in the folder the path name indicates.
         * @param args args[0] is the directory for the eigenvalue and eigenvector files. args[1] is the directory
         * where the HKS values should be stored.
         */
        static void Main(string[] args)
        {
            if (args.Length > 1)
            {
                string storageDir = args[0];
                string[] fileEntries = Directory.GetFiles(args[0]);
                int count = 0;
                foreach (string fileName in fileEntries)
                {
                    if (fileName.EndsWith(".vect"))
                    {
                        CalculateHKT(fileName, fileName.Replace(".vect", ".eval"), args[1]);
                        Console.WriteLine(++count);
                    }
                }

                Storage s = new Storage(storageDir);
                int fileCount = s.getNumberOfFiles();
                String[] filenames = s.getKeys();
                List<String> l = filenames.ToList<String>();
                l.Sort();
                filenames = l.ToArray();
                double[,] distances = new double[fileCount, fileCount];
                for (int i = 0; i < fileCount; i++)
                    for (int j = 0; j < fileCount; j++)
                    {
                        double[] firstHKS, secondHKS;
                        firstHKS = s.getHKSValues(filenames[i]);
                        secondHKS = s.getHKSValues(filenames[j]);
                        distances[i, j] = euclidianDistance(firstHKS, secondHKS);
                    }

                StreamWriter sw = new StreamWriter(storageDir + "\\HKSComparisonMatrix.csv");
                sw.Write("Name,");
                for (int i = 0; i < fileCount; i++)
                {
                    sw.Write(filenames[i]);
                    if (i < fileCount - 1)
                        sw.Write(",");
                }
                sw.WriteLine();
                for (int i = 0; i < fileCount; i++)
                {
                    sw.Write(filenames[i] + ",");
                    for (int j = 0; j < fileCount; j++)
                    {
                        sw.Write(("" + distances[i, j]).Replace(",", "."));
                        if (j < fileCount - 1)
                            sw.Write(",");
                    }
                    sw.WriteLine();
                }
                sw.Flush();
                sw.Close();

                //ComparisonCalculator.CalculateComparisonLists(storageDir + "\\HKSComparisonMatrix.csv", "null");
            }
        }

        #endregion
    }
}
